package com.genreate.util.zos;

import cn.hutool.json.JSONException;
import cn.hutool.json.JSONObject;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

public class PostObjectSDK {
    private final String accessKeyId;
    private final String accessKeySecret;
    private final String endpoint;

    public PostObjectSDK(String ak, String sk, String endpoint) {
        this.accessKeyId = ak;
        this.accessKeySecret = sk;
        this.endpoint = endpoint;
    }

    public static class PostObjectRequest {
        private String Url;
        private String FilePath;
        private Map<String, String> FormFields;

        public PostObjectRequest(String url, String filePath, Map<String, String> formFields) {
            this.Url = url;
            this.FilePath = filePath;
            this.FormFields = formFields;
        }

        public String getUrl() {
            return Url;
        }

        public String getFilePath() {
            return FilePath;
        }

        public Map<String, String> getFormFields() {
            return FormFields;
        }

        public void addFormFields(String key, String value) {
            FormFields.put(key, value);
        }
    }

    public static class PostObjectResult {
        private final int responseCode;
        private final String message;

        public PostObjectResult(int responseCode, String message) {
            this.responseCode = responseCode;
            this.message = message;
        }

        public int getResponseCode() {
            return responseCode;
        }

        public String getMessage() {
            return message;
        }
    }

    public static class PostObjectPolicy {
        private String expiration;
        private List<Object> conditions = new ArrayList<>();

        public PostObjectPolicy() {
            setExpiration(900);
        }

        public void addEqualCondition(String property, String value) {
            conditions.add(Arrays.asList("eq", "$" + property, value));
        }

        public void addMatchCondition(String property, String value) {
            conditions.add(Arrays.asList("starts-with", "$" + property, value));
        }

        public void setExpiration(int expiredSecond) {
            Date now = new Date();
            Date expire = new Date(now.getTime() + expiredSecond * 1000L);

            DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
            dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
            expiration = dateFormat.format(expire);
        }

        public String getPolicy() throws JSONException {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("expiration", expiration);
            jsonObject.put("conditions", conditions);
            return jsonObject.toString();
        }
    }

    static class Signature {
        private final static char[] hexArray = "0123456789ABCDEF".toCharArray();

        public static String bytesToHex(byte[] bytes) {
            char[] hexChars = new char[bytes.length * 2];
            for (int j = 0; j < bytes.length; j++) {
                int v = bytes[j] & 0xFF;
                hexChars[j * 2] = hexArray[v >>> 4];
                hexChars[j * 2 + 1] = hexArray[v & 0x0F];
            }
            return new String(hexChars).toLowerCase();
        }

        public static byte[] HmacSHA256(byte[] key, String data) throws Exception {
            String algorithm = "HmacSHA256";
            Mac mac = Mac.getInstance(algorithm);
            mac.init(new SecretKeySpec(key, algorithm));
            return mac.doFinal(data.getBytes("UTF8"));
        }

        public static String compute(String currentDate, String Region,
                                     String accessKeySecret, String encodePolicy) throws Exception {
            String serviceName = "s3";
            byte[] kSecret = ("AWS4" + accessKeySecret).getBytes("UTF8");
            byte[] kDate = Signature.HmacSHA256(kSecret, currentDate);
            byte[] kRegion = Signature.HmacSHA256(kDate, Region);
            byte[] kService = Signature.HmacSHA256(kRegion, serviceName);
            byte[] kSigning = Signature.HmacSHA256(kService, "aws4_request");
            byte[] signature = Signature.HmacSHA256(kSigning, encodePolicy);
            return Signature.bytesToHex(signature);
        }
    }

    private String getDate(Date date) {
        DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));//server timezone
        return dateFormat.format(date);
    }

    private String getTimeStamp(Date date) {
        DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));//server timezone
        return dateFormat.format(date);
    }

    public PostObjectRequest generatePostObjectRequest(String bucketName, String objectKey, String filePath, PostObjectPolicy policy) throws Exception {
        String urlStr = endpoint + "/" + bucketName;

        Date date = new Date();
        String currentDate = getDate(date);
        String amzDate = getTimeStamp(date);
        String region = "cn";
        String success_action_status = "201";
        String credential = accessKeyId + "/" + currentDate + "/" + region + "/s3/aws4_request";

        Map<String, String> formFields = new HashMap<>();
        formFields.put("key", objectKey);
        formFields.put("x-amz-algorithm", "AWS4-HMAC-SHA256");
        formFields.put("x-amz-credential", credential);
        formFields.put("x-amz-date", amzDate);
        formFields.put("success_action_status", success_action_status);

        policy.addEqualCondition("bucket", bucketName);
        policy.addMatchCondition("key", objectKey);
        policy.addEqualCondition("x-amz-algorithm", "AWS4-HMAC-SHA256");
        policy.addEqualCondition("x-amz-credential", credential);
        policy.addEqualCondition("x-amz-date", amzDate);
        policy.addEqualCondition("success_action_status", success_action_status);
        String encodePolicy = new String(Base64.getEncoder().encodeToString(policy.getPolicy().getBytes()));
        formFields.put("Policy", encodePolicy);

        // Signature
        String signaturecom = Signature.compute(currentDate, region, accessKeySecret, encodePolicy);
        formFields.put("x-amz-signature", signaturecom);

        return new PostObjectRequest(urlStr, filePath, formFields);
    }

    public static PostObjectResult postObject(PostObjectRequest request) throws Exception {
        HttpURLConnection conn = null;
        int responseCode = -1;
        String message = "";
        try {
            String boundary = "9431149156168";
            URL url = new URL(request.getUrl());
            conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(30000);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setRequestMethod("POST");
            conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)");
            conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

            OutputStream out = new DataOutputStream(conn.getOutputStream());

            Map<String, String> formFields = request.getFormFields();
            if (formFields != null) {
                StringBuilder strBuf = new StringBuilder();
                int i = 0;
                for (Map.Entry<String, String> entry : formFields.entrySet()) {
                    String inputName = entry.getKey();
                    String inputValue = entry.getValue();
                    if (inputValue == null) continue;
                    if (i > 0) {
                        strBuf.append("\r\n");
                    }
                    strBuf.append("--").append(boundary).append("\r\n");
                    strBuf.append("Content-Disposition: form-data; name=\"").append(inputName).append("\"\r\n\r\n");
                    strBuf.append(inputValue);
                    i++;
                }
                out.write(strBuf.toString().getBytes());
            }

            String filePath = request.getFilePath();
            File file = new File(filePath);
            String filename = file.getName();
            Path path = Paths.get(filePath);
            String contentType = Files.probeContentType(path);
            if (contentType == null || contentType.equals("")) {
                contentType = "application/octet-stream";
            }
            StringBuilder strBuf = new StringBuilder();
            strBuf.append("\r\n--").append(boundary).append("\r\n");
            strBuf.append("Content-Disposition: form-data; name=\"file\"; filename=\"").append(filename).append("\"\r\n");
            strBuf.append("Content-Type: ").append(contentType).append("\r\n\r\n");
            out.write(strBuf.toString().getBytes());
            DataInputStream in = new DataInputStream(new FileInputStream(file));
            int bytes = 0;
            byte[] bufferOut = new byte[1024];
            while ((bytes = in.read(bufferOut)) != -1) {
                out.write(bufferOut, 0, bytes);
            }
            in.close();

            byte[] endData = ("\r\n--" + boundary + "--\r\n").getBytes();
            out.write(endData);
            out.flush();
            out.close();

            strBuf = new StringBuilder();
            BufferedReader reader = null;
            responseCode = conn.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_CREATED || responseCode == HttpURLConnection.HTTP_NO_CONTENT) {
                reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            } else {
                reader = new BufferedReader(new InputStreamReader(conn.getErrorStream()));
            }
            String line = null;
            while ((line = reader.readLine()) != null) {
                strBuf.append(line).append("\n");
            }
            message = strBuf.toString();
            reader.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
        return new PostObjectResult(responseCode, message);
    }
}